const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { Types } = require('mongoose');
const ProgressIndicator = require('../../../progress-indicator');
const { getVersion } = require('../../../project-versions');
const BaseReport = require('../base-report');
const classCreator = require('./../base-report-test-case-transformer');
const { resultKind } = require('../../../tests');
const testModels = require('./../../../tests');
const functionModels = require('./../../../functions');
const utility = require('../../../utility');
const logger = require('../../../logger');
const { getCountableKinds } = require('../../../file-elements');

class Report8357 extends BaseReport {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format = null, complete = false) {
    super(versionId, locale, version, project, creator, outputDir, null, format);
    this.baseDir = __dirname;
    this.complete = complete;
    this.ReportTestCaseTransformer = classCreator('unitTest');

    this.fileList = [];
    // this.testCaseList = [];
    this.coverageKind = [
      {
        key: 'statement', name: '语句',
      },
      {
        key: 'branch', name: '分支',
      },
    ];

    // this.functionList = [];

    this.reportTime = new Date();
  }

  isRegressionTest() {
    return this.version.oldVersionId !== undefined;
  }

  async generate(progressIndicator = new ProgressIndicator()) {
    const [progress1, progress2] = progressIndicator.split([0.7, 0.3]);
    const result = {
      cachePath: this.outputDir,
    };
    await this.generateData(progress1);
    await this.generateSummaryReport();
    const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}.${this.format}.zip`;
    result.reportPath = path.join(this.outputDir, fileName);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    progress2.finish();
    return result;
  }

  async generateData(progressIndicator) {
    //
    await this.spliteByFile(null, progressIndicator);
  }

  async generateSummaryReport() {
    const template = 'summary';
    const output = path.join(
      this.outputDir,
      this.project.projectName,
      this.version.versionName,
      `${this.project.projectName + (this.isIntegrationTest() ? '集成' : '单元')}测试报告`
    );
    // 当前是回归版本，生成原始版本数据
    let reportOld;
    if (this.isRegressionTest()) {
      const { oldVersionId } = this.version;
      const oldVersion = await getVersion(oldVersionId);
      const report = new Report8357(oldVersionId, this.i18n, oldVersion, this.project, this.creator, this.outputDir, this.format, this.complete);
      await report.generateData();
      // console.log(report);
      reportOld = report;
    }
    const functionTraceList = [];
    let globalIdx = 0;
    this.fileList.forEach((f) => {
      const { functionList } = f;
      functionList.forEach((func) => {
        functionTraceList.push({
          ...func,
          index: ++globalIdx,
        });
      });
    });
    await this.generateReport(template, {
      fileList: this.fileList,
      fileListOriginal: this.isRegressionTest() ? reportOld.fileList : [],
      testTime: moment(Types.ObjectId(this.project._id).getTimestamp()).format('YYYYMMDD'),
      isRegressionTest: this.isRegressionTest(),
      functionTraceList,
    }, output);
  }

  getCoverage(item) {
    const coverages = {};
    this.coverageKind.forEach(({ key }) => {
      const covered = item[`${key}Covered`] || 0;
      const total = item[`${key}Total`] || 0;
      const value = total ? Math.round((covered / total) * 100, 10) : '-';
      coverages[`${key}Coverage`] = value === '-' ? value : `${value}%`;
    });
    return coverages;
  }

  async handleFile(file, versionDir) {
    if (file.toObject) {
      file = file.toObject();
    }
    const result = await super.handleFile(file, versionDir);
    // console.log(result);
    const { functions: functionAll, typeSystem } = result;
    const functions = functionAll.filter((func) => {
      if (!getCountableKinds().find((kind) => kind === func.kind)) {
        return false;
      }
      func.tests = func.tests.filter((t) => t.resultKind !== resultKind.unknown); // 未执行的不统计
      return func.tests.length;
    });
    if (functions.length) {
      const functionDetails = await this.getFunctionDetailsOfFile(functions, typeSystem, file);
      file.functionList = functionDetails;
      this.fileList.push(file);
    }
  }

  /**
     *  获取函数详情
     * @param {Array[Object]} functions 函数
     * @param {Object} typeSystem 文件类型系统
     */
  async getFunctionDetailsOfFile(functions, typeSystem, file) {
    const functionsDetails = [];
    await utility.arrayChunkOperation(functions, 10, async (func) => {
      if (func.tests.length === 0) {
        return;
      }
      const funcVariables = functionModels.getFunctionVariableJson(func.variables);
      const functionCFG = await functionModels.getFunctionCFG(func._id);
      const testcasesDetails = await this.getTestDetailsOfFunction(
        typeSystem,
        func,
        funcVariables,
        func.tests,
        file,
        {
          branches: functionCFG.branches,
          decisions: functionCFG.decisions,
          nodes: functionCFG.nodes,
        }
      );
      const coverages = this.getCoverage(func);
      if (testcasesDetails && testcasesDetails.length) {
        const [first] = testcasesDetails;
        const last = testcasesDetails[testcasesDetails.length - 1];
        functionsDetails.push({
          functionName: func.functionName,
          functionFullName: func.functionFullName,
          index: func.index,
          tests: testcasesDetails,
          ...coverages,
          testIdentifiers: testcasesDetails.length > 1 ? `${first.identifier}-${last.identifier}` : `${first.identifier}`,
        });
      }
    });
    return functionsDetails;
  }

  /**
   * 测试结果数字类型对应的名称
   * @param {Number} kind 测试结果类型
   * @returns {Object}
   */
  static getTestResultKindKey(kind) {
    switch (kind) {
      case resultKind.unknown:
        return 'unknown';
      case resultKind.succeed:
      case resultKind.nonStrictSucceed:
      case resultKind.noResult:
        return 'succeed';
      case resultKind.failed:
        return 'failed';
      case resultKind.runtimeError:
      case resultKind.timeout:
        return 'codeDefect';
      default:
        return 'exception';
    }
  }

  toPercentage(decimal, decimalPlaces) {
    if (typeof decimal !== 'number' || typeof decimalPlaces !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    return Math.round(decimal * 100 * (10 ** decimalPlaces)) / 100;
  }

  /**
   * 获取测试用例分支预期结果表达式信息
   * @param {String} testcaseId 测试用例id
   * @param {Object} functionCFG 函数cfg
   */
  getTestExpectedResultDetails(testcaseId, functionCFG) {
    const { branches, decisions, nodes } = functionCFG;
    if (branches
      && Object.keys(branches).length > 0
      && nodes
      && Object.keys(nodes).length > 0
      && decisions
      && Object.keys(decisions).length > 0) {
      const branchExprs = [];
      Object.keys(branches).forEach((branchKey) => {
        const branch = branches[branchKey];
        if (branch.decisionId === undefined || !decisions[branch.decisionId]) {
          return;
        }
        const { expr } = decisions[branch.decisionId];
        Object.keys(branch.branches).forEach((nodeId) => {
          const branchNode = branch.branches[nodeId];
          if (branchNode
            && branchNode.kind === 'BOOLEAN'
            && branchNode.testIds
            && branchNode.testIds[testcaseId]
            && branchNode.testIds[testcaseId] > 0
            && nodes[nodeId]
            && nodes[nodeId].begin) {
            branchExprs.push({
              expr: `程序执行${expr}分支为${branchNode.value === '1'},进入${nodes[nodeId].begin.line || ''}行;`,
            });
          }
        });
      });
      return branchExprs;
    }
    return [];
  }

  /**
   * 获取测试用例详情
   * @param {Object} typeSystem 文件类型系统
   * @param {Object} func  函数
   * @param {Object} funcVariables 函数variable
   * @param {Array[Object]} testcases 测试用例
   */
  async getTestDetailsOfFunction(typeSystem, func, funcVariables, testcases, file, functionCFG) {
    const testcasesDetails = [];
    testcases
      // eslint-disable-next-line max-len
      // .filter((tc) => [resultKind.succeed, resultKind.noResult, resultKind.nonStrictSucceed, resultKind.failed].find((item) => item === tc.resultKind))
      .forEach((testcase, index) => {
        const newTestcase = testModels.getJson(testcase, this.i18n);
        const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
        const transformer = new ReportTestCaseTransformer(
          newTestcase,
          funcVariables,
          typeSystem,
          this.i18n
        );
        try {
          transformer.transform(this.complete);
        } catch (error) {
          logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${testcase.number}:`, error);
        }
        if (utility.isArrayType(transformer.input)) {
          transformer.input.forEach((input) => {
            // eslint-disable-next-line no-control-regex
            input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
            // 处理变量值显示格式
            if (input.value) {
              input.value = ` = ${input.value}`;
            }
          });
        }
        if (utility.isArrayType(transformer.output)) {
          transformer.output.forEach((output) => {
            // eslint-disable-next-line no-control-regex
            output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
            // 处理变量值显示格式
            output.value = output.value || output.result; // 预期值没填的，把实际值赋给预期值
            if (output.result) {
              output.result = ` = ${output.result}`;
            }
            if (output.value) {
              output.value = ` = ${output.value}`;
            }
          });
        }
        const newInputDetails = [];
        const newOutputDetails = [];
        transformer.input.forEach((vobj) => {
          if (vobj.category && vobj.variable) {
            newInputDetails.push({
              category: `${vobj.category}:`,
              value: '',
              variable: '',
            });
            newInputDetails.push({
              category: '',
              value: vobj.value,
              variable: vobj.variable,
            });
          } else {
            newInputDetails.push(vobj);
          }
        });
        transformer.output.forEach((vobj) => {
          if (vobj.category && vobj.variable) {
            newOutputDetails.push({
              category: `${vobj.category}:`,
              value: '',
              result: '',
              variable: '',
            });
            newOutputDetails.push({
              category: '',
              value: vobj.value,
              result: vobj.result,
              variable: vobj.variable,
            });
          } else {
            newOutputDetails.push(vobj);
          }
        });
        const expectedResultDetails = this.getTestExpectedResultDetails(testcase._id.toString(), functionCFG);
        const item = {
          index: index + 1,
          testId: testcase._id,
          resultKind: this.i18n.__(`tests.testResult.${Report8357.getTestResultKindKey(
            testcase.resultKind === testModels.resultKind.noResult ? testModels.resultKind.succeed : testcase.resultKind
          )}`),
          details: {
            input: newInputDetails.length === 0 ? [{ category: '', variable: '', value: '-' }] : newInputDetails,
            output: newOutputDetails.length === 0 ? [{
              category: '', variable: '', result: '-', value: '-',
            }] : newOutputDetails,
          },
          expectedResultDetails,
          identifier: `test_${func.functionFullName}_${`${index + 1}`.padStart(3, '0')}`,
          functionId: func._id,
          filePath: file.path,
          fileName: file.fileName,
        };
        testcasesDetails.push(item);
      });
    return testcasesDetails;
  }
}

module.exports = Report8357;
